'''
main.py
Nicholas Swartzendruber

This is the main script for the class. It will execute
all the others. 
'''
import time
import bitly
import os
import random
from log import Log
from content import Content
from twitter_account import TwitterAccount
from datetime import datetime, timedelta
from constants import *
from keywords import Keyword

#The time to start the bot.
BOT_START = datetime(2011, 5, 26, 13, 30)

#The time to end the bot.
BOT_END = datetime(2011, 5, 31, 13, 30)

#The latest time to send tweets. (22 = 10pm stop)
LATEST_TIME = 23

#The earliest time to start. (4 = 4am)
EARLIEST_TIME = 4

#The time that we are allowed to start targeting users with directed tweets.
START_TARGETING = datetime(2011, 5, 26, 13, 30)

#Minimum minutes to wait before activity.
MIN_MINUTE_WAIT = 50

#Max minutes to wait before activity.
MAX_MINUTE_WAIT = 80

#The seconds to wait before interaction with twitter +- 10sec.
INTERACTION_DELAY = 60

#The max number of targeted tweets we can send per MINUTE_WAIT interval.
MAX_TARGETED_TWEETS = 6

#The min number of targeted tweets we can send per MINUTE_WAIT interval.
MIN_TARGETED_TWEETS = 3

#The time to wait between doing a batch of targeted tweets (in min).
TARGETED_DELAY = 45

#Key used to store the last time we sent a targeted tweet.
LAST_TARGET_KEY = 'last_target'

#The time to wait before doing a general tweet in HOURS.
GENERAL_DELAY = 3

#Key used to store the last time we sent a general tweet
LAST_GENERAL_KEY = 'last_general'

#The bitly API object
BITLY = bitly.Api(login='katyflesher', apikey=BITLY_KEY) 

#The file that holds stories we have read.
STORIES_FILE = 'used_stories.txt'

def sysTime():
  '''
  Returns the current system time in ms.
  '''
  return int(time.time() * 1000)

def constructTweet(articles, used, twitter, uid = False):
  '''
  When passed a series of reddit articles, a twitter object
  , a set of titles that have been used, and a user ID, 
  a tweet will be constructed and returned. If uid is blank, an @
  will be formed. None will be returned if nothing good is found.
  '''
  #21 = bit.ly url length + 1 space.
  extra_space = 21
  username = ''
  if uid != False:
    username = '@' + twitter.getUsername(uid) + ' '
    #Add length of user name + @ + space.
    extra_space += len(username)
    
  #Clear any stories that won't work.
  for story in articles:
    #If the title is too long to make a tweet or already done, remove it.
    if len(story['data']['title']) > 140 - extra_space or story['data']['title'] in used or story['data']['subreddit'] == 'IAmA':
      articles.remove(story)
  
  #Make sure we still have articles left.
  if len(articles) > 0:
    story = random.choice(articles)
    
    #Add the new story to our used set.
    used.add(story['data']['title'])
    with open(STORIES_FILE, 'a+') as f:
      f.write(story['data']['title'] + '\n')
    
    #Don't make a url if it's to reddit.
    url = ''
    if 'reddit' not in story['data']['url']:  
      url = BITLY.shorten(story['data']['url'])
    
    #Generate the tweet and return it.
    tweet = username + story['data']['title'] + " " + url
    return tweet
  else:
    return None

if __name__ == "__main__":
  content = Content()
  twitter = TwitterAccount()
  keywords = Keyword('keywords.data')
  
  #Load all of the stories that have already been viewed into a set.
  used = set()
  
  Log.println('Starting Twitter Bot.')
  
  #Load all of the valid subreddits.
  if os.path.exists(STORIES_FILE):
    with open(STORIES_FILE, 'r') as f:
      while 1:
        line = f.readline()
        if not line:
          break
        line = line.split('\n')[0]
        if len(line) > 0:
          used.add(line)

  now = datetime.now()
  
  while 1:
    #The main loop.
    while now < BOT_END and now > BOT_START:
      now = datetime.now()
          
      #Find out how long is ms has passed since we last finished targeting.
      target_diff = sysTime() - int(Log.get(LAST_TARGET_KEY))
      
      #Grab the number of targeted tweets we want to do on this interval.
      if target_diff >= TARGETED_DELAY * 60 * 1000:
        targeted_tweets_left = random.randrange(MIN_TARGETED_TWEETS, MAX_TARGETED_TWEETS + 1)
      else:
        targeted_tweets_left = 0
        
      #Refresh our targets and friends
      twitter.refreshTargetsAndFriends()
      
      while ((targeted_tweets_left > 0 and twitter.hasTargetsLeft()) or sysTime() - int(Log.get(LAST_GENERAL_KEY)) > GENERAL_DELAY * 60 * 60 * 1000) and (now.hour > EARLIEST_TIME and now.hour < LATEST_TIME):
        if targeted_tweets_left > 0 and twitter.hasTargetsLeft():
          Log.println("Targeted tweets left for this interval: " + str(targeted_tweets_left));
          
        #Pick a random user that we need to follow.
        uid = twitter.getNewFriend()
        
        #Follow them.
        twitter.follow(uid)
        
        #Is it time to start targeting people? If so:
        if now > START_TARGETING and targeted_tweets_left > 0 and twitter.hasTargetsLeft():  
          Log.println('---Starting a directed tweet---')
          
          #Decrement our tweets left.
          targeted_tweets_left -= 1
          
          #Grab our target
          target = twitter.getNewTarget()
          
          twitter.follow(target);
          
          #Find some keywords.
          keyword = keywords.get_keyword(twitter.getTwitterObject(), target)
          
          if keyword:
            keyword = keyword[1]
          
            #See if we can find related articles.
            articles = content.getTargetContent(keyword)
            
            #Yes? Then grab a NEW one.
            if len(articles) > 0:
            
              #Pick an article and construct a tweet.
              tweet = constructTweet(articles, used, twitter, target)
              
              #Send it out.
              if tweet != None:
                twitter.post(tweet)
            
            #update last tweet time.
            Log.put(LAST_TARGET_KEY, sysTime())
            if targeted_tweets_left == 0:
              Log.println("Targeted tweets completed. Estimated soonest time to restart: " + str((now + timedelta(seconds = float(TARGETED_DELAY * 60)))))
          
        #Is the gap larger than GENERAL_DELAY hours?
        elif sysTime() - int(Log.get(LAST_GENERAL_KEY)) > GENERAL_DELAY * 60 * 60 * 1000:
          Log.println('---Starting a general tweet---')
          
          #Find a new general article
          articles = content.getGeneralContent()
      
          #Generate the tweet
          tweet = constructTweet(articles, used, twitter)
          
          #Send it out.
          if tweet != None:
              twitter.post(tweet)
              
          #update last tweet time.
          Log.put(LAST_GENERAL_KEY, sysTime())
          Log.println("General tweet completed. Estimated soonest time to restart: " + str((now + timedelta(seconds = float(GENERAL_DELAY * 60 * 60)))))
          
        sleep_min_interval = random.randrange(INTERACTION_DELAY - 10, INTERACTION_DELAY + 11)
        Log.println("Sleeping for " + str(sleep_min_interval) + " seconds. Delay before next action.")
        #Pause for an arbitrary human-like time (A few minutes?)
        time.sleep(sleep_min_interval)
        
        #Have API calls left? Do another tweet.
        api = twitter.getAuthInfo()
        
        if api['remaining_hits'] < 20:
          Log.println("Rate limit getting close -- going to sleep.")
          break
        
      minutes = random.randrange(MIN_MINUTE_WAIT, MAX_MINUTE_WAIT + 1)
      Log.println("Finished current batch, sleeping for " + str(minutes) + " minutes.")
      time.sleep(minutes * 60)
    
    #Inside of the while 1. Prints if we are out of range.
    if now > BOT_END or now < BOT_START:
      Log.println("--Bot is only allowed to run between " + str(BOT_START) + " and " + str(BOT_END) + ". Sleeping for 30min.--")
      time.sleep(30 * 60)
